package com.zhuangjie.coupon.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhaungjie.base.conduit.TypeR;
import com.zhaungjie.base.utils.BigDecimalUtils;
import com.zhaungjie.base.utils.json.JsonUtils;
import com.zhaungjie.base.utils.LambdaStructureParser.LambdaStructureParser;
import com.zhaungjie.base.utils.ReflectionUtils;
import com.zhuangjie.coupon.dto.LockCouponsDTO;
import com.zhuangjie.coupon.entity.Coupon;
import com.zhuangjie.coupon.entity.CouponBatch;
import com.zhuangjie.coupon.enums.CouponBatchStatus;
import com.zhuangjie.coupon.enums.CouponObtainWay;
import com.zhuangjie.coupon.enums.CouponState;
import com.zhuangjie.coupon.enums.IssuedType;
import com.zhuangjie.coupon.feign.ShopFeignService;
import com.zhuangjie.coupon.mapper.CouponMapper;
import com.zhuangjie.coupon.pojo.CouponCoreInfo;
import com.zhuangjie.coupon.pojo.CouponKey;
import com.zhuangjie.coupon.pojo.RawValidCouponCount;
import com.zhuangjie.coupon.service.CouponProductBatchService;
import com.zhuangjie.coupon.service.CouponService;
import com.zhuangjie.coupon.vo.ApplicableShopCouponCoreInfoVO;
import com.zhuangjie.coupon.vo.ValidCouponCount;
import com.zhuangjie.mvc.future.ContextAwareExecutor;
import com.zhuangjie.security.provider.UserProvider;
import com.zhuangjie.shop.vo.ShopDisplayInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhuangjie
 * @since 2024-02-28
 */
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {

    @Autowired
    private BigDecimalUtils bigDecimalUtils;

    @Autowired
    private ContextAwareExecutor contextAwareExecutor;

    @Autowired
    private ShopFeignService shopFeignService;

    @Override
    public Collection<CouponCoreInfo> queryMyPlatformCoupons(boolean isFold, Long userId, Long shopId) throws ExecutionException, InterruptedException {
        CompletableFuture<Boolean> isShopInvolvedInPlatformCouponActivityCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                if (shopId == null) return true;
                return TypeR.extract(shopFeignService.isShopInvolvedInPlatformCouponActivity(shopId));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        },contextAwareExecutor);
        CompletableFuture<List<CouponCoreInfo>> myPlatformCouponsCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return this.baseMapper.queryIssuedTypeAllEffectiveCoupons(userId,null, IssuedType.PLATFORM_COUPON.getCode(), CouponState.UNUSED.getCode());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        },contextAwareExecutor);
        CompletableFuture.allOf(isShopInvolvedInPlatformCouponActivityCompletableFuture,myPlatformCouponsCompletableFuture);
        // 店铺是否支持神券活动
        Boolean isShopInvolvedInPlatformCouponActivity = isShopInvolvedInPlatformCouponActivityCompletableFuture.get();
        List<CouponCoreInfo> myPlatformCoupons = myPlatformCouponsCompletableFuture.get();
        if (myPlatformCoupons == null) {
            // 如果没有，那就返回一个空的数组
            return new ArrayList<>();
        }
        if (! isShopInvolvedInPlatformCouponActivity) {
            // 如果店铺不支持神券，那就降级
            couponDowngrading(myPlatformCoupons);
        }
        if(isFold) {
            return couponFold(myPlatformCoupons);
        }
        return myPlatformCoupons;
    }

    /**
     * 优惠券降级
     * @param coupons
     * @return
     */
    private static Collection<CouponCoreInfo> couponFold(List<CouponCoreInfo> coupons) {
        Map<CouponKey, CouponCoreInfo> couponKeyCouponVOHashMap = new HashMap<>();
        for (CouponCoreInfo coupon : coupons) {
            CouponKey couponKey = new CouponKey(coupon.getBatchId(), coupon.getExpansionAmount(), coupon.getIsStackable());
            CouponCoreInfo couponVO = couponKeyCouponVOHashMap.get(couponKey);
            if (couponVO == null) {
                couponKeyCouponVOHashMap.put(couponKey, coupon);
            } else {
                Integer count = couponVO.getCount();
                if (count == null) count = 1;
                couponVO.setCount(count + 1);
            }
        }
        return couponKeyCouponVOHashMap.values();
    }

    @Override
    public void expandCoupon(Long couponId) {
        // 1. 查询条件
        Long userId = UserProvider.currentUserId();
        // 要求查询的是平台卷
        byte issuedType = IssuedType.PLATFORM_COUPON.getCode();
        // 查询的优惠卷状态需要是未使用
        byte couponState = CouponState.UNUSED.getCode();
        // 2. 使用查询条件查询出CouponVO
        CouponCoreInfo couponVO = this.baseMapper.queryValidExpandableCoupons(userId, couponId, couponState, issuedType);
        // 3. 当没有查询到CouponVO，说明不满足膨胀条件，抛出异常
        if (couponVO == null) {
            throw new RuntimeException("该优惠卷不满足膨胀条件！");
        }
        // 4. 进行膨胀
        // 优惠卷面值（这里面值必然不为空，在xml中已经判断）
        BigDecimal deductionAmount = couponVO.getParValue();
        // 膨胀规则（这里面值必然不为空，在xml中已经判断）
        String expansionRule = couponVO.getExpansionRule();
        BigDecimal expansionAmount = couponExpansion(expansionRule);
        BigDecimal expansionDeductionAmount = bigDecimalUtils.subtractionOperation(expansionAmount, couponVO.getParValue());
        if (expansionDeductionAmount.compareTo(BigDecimal.ZERO) < 0) {
            expansionDeductionAmount = BigDecimal.ZERO;
        }
        // 5. 更新优惠卷的膨胀金额（n+m的m）coupon.expansionDeductionAmount
        LambdaUpdateWrapper<Coupon> setExpansionDeductionAmountUpdateWrapper = Wrappers.lambdaUpdate(Coupon.class)
                .eq(Coupon::getId, couponId)
                .set(Coupon::getExpansionAmount, expansionDeductionAmount);
        boolean updateSuccess = this.update(setExpansionDeductionAmountUpdateWrapper);
        if (!updateSuccess) {
            throw new RuntimeException("更新优惠卷的膨胀金额失败！");
        }
    }

    @Override
    public ValidCouponCount validCouponCount(Long userId) {
        // 查出RawValidCouponCount
        List<RawValidCouponCount> rawValidCouponCounts = this.baseMapper.validCouponCount(userId, CouponState.UNUSED.getCode());
        // List<RawValidCouponCount> -> ValidCouponCount
        ValidCouponCount validCouponCount = new ValidCouponCount();
        for (RawValidCouponCount rawValidCouponCount : rawValidCouponCounts) {
            if (rawValidCouponCount == null || rawValidCouponCount.getIssueType() == null) continue;
            if (rawValidCouponCount.getIssueType().equals(IssuedType.PLATFORM_COUPON.getCode())) {
                validCouponCount.setValidPlatformCouponCount(rawValidCouponCount.getCount());
            } else if (rawValidCouponCount.getIssueType().equals(IssuedType.SHOP_COUPON.getCode())) {
                validCouponCount.setValidShopCouponCount(rawValidCouponCount.getCount());
            }
        }
        return validCouponCount;
    }



    @Autowired
    private CouponProductBatchService couponProductBatchService;

    /**
     * 店铺页面的平台优惠券
     *
     * @param userId                                           用户id
     * @param isParticipatingInPlatformInflationCouponActivity 正在参与平台通货膨胀优惠券活动
     * @return {@link List}<{@link CouponCoreInfo}>
     */
    @Override
    public List<CouponCoreInfo> queryReceivedPlatformCoupon(Long userId, Boolean isParticipatingInPlatformInflationCouponActivity) {
        List<CouponCoreInfo> couponCoreInfos = this.baseMapper.queryReceivedPlatformCoupon(userId, IssuedType.PLATFORM_COUPON.getCode(), CouponState.UNUSED.getCode());
        if (couponCoreInfos == null) {
            return new ArrayList<>();
        }
        // 折叠
        couponCoreInfos = couponCoreInfos.stream().distinct().collect(Collectors.toList());
        genericPostProcessorForQueryPlatformCoupon(isParticipatingInPlatformInflationCouponActivity, couponCoreInfos);
        return couponCoreInfos;
    }

    /**
     * 查询的是已领取或可以领取（店铺展示的）券
     * “确认页-依赖"
     * @param userId
     * @param shopId
     * @return
     */
    @Override
    public List<CouponCoreInfo> queryApplicableCouponsForShop(Long userId, Long shopId) {
        return this.baseMapper.queryUserShopCoupon(userId, shopId, CouponState.UNUSED.getCode(), CouponObtainWay.SHOP_DISPLAY.getCode(), CouponBatchStatus.CLAIMABLE.getCode());
    }

    /**
     * 查询的是用户已领取/可通过coupon-product购买的券
     * "确认页-依赖"
     * @param userId
     * @param isParticipatingInPlatformInflationCouponActivity
     * @return
     */
    public List<CouponCoreInfo> queryReceivedOrPurchasablePlatformCoupon(Long userId, Boolean isParticipatingInPlatformInflationCouponActivity) {
        // 查询产品关联的所有券批次
        List<Long> productCouponBatchIds = couponProductBatchService.queryAllListedProductAssociatedCouponBatchIds();
        List<CouponCoreInfo> couponCoreInfos = this.baseMapper.queryReceivedOrPurchasablePlatformCoupon(userId, IssuedType.PLATFORM_COUPON.getCode(), CouponState.UNUSED.getCode(), productCouponBatchIds, CouponBatchStatus.CLAIMABLE.getCode());
        if (couponCoreInfos == null) {
            return new ArrayList<>();
        }
        genericPostProcessorForQueryPlatformCoupon(isParticipatingInPlatformInflationCouponActivity, couponCoreInfos);
        return couponCoreInfos;
    }

    /**
     * 查询平台券需要用到的通用后置处理器
     *
     * @param isParticipatingInPlatformInflationCouponActivity
     * @param couponCoreInfos
     */
    private static void genericPostProcessorForQueryPlatformCoupon(Boolean isParticipatingInPlatformInflationCouponActivity, List<CouponCoreInfo> couponCoreInfos) {
        if (!isParticipatingInPlatformInflationCouponActivity) couponDowngrading(couponCoreInfos);
        couponSort(couponCoreInfos);
    }

    /**
     * 优惠券排序
     *
     * @param couponCoreInfos
     */
    private static void couponSort(List<CouponCoreInfo> couponCoreInfos) {
        // 排序从可用金额从高到低
        couponCoreInfos.sort((o1, o2) -> {
            BigDecimal o1Amount = o1.getParValue();
            if (o1.getExpansionAmount() != null) o1Amount = o1Amount.add(o1.getExpansionAmount());
            BigDecimal o2Amount = o2.getParValue();
            if (o2.getExpansionAmount() != null) o2Amount = o2Amount.add(o2.getExpansionAmount());
            return o2Amount.compareTo(o1Amount);
        });
    }

    /**
     * 神券降级
     *
     * @param couponCoreInfos
     */
    private static<T> void couponDowngrading(List<T> couponCoreInfos) {
        try {
            for (Object couponCoreInfo : couponCoreInfos) {
                Field expansionRuleField = ReflectionUtils.getField(couponCoreInfo.getClass(), LambdaStructureParser.convertToFieldName(CouponBatch::getExpansionRule));
                expansionRuleField.setAccessible(true);
                expansionRuleField.set(couponCoreInfo, null);

                Field expansionAmountField = ReflectionUtils.getField(couponCoreInfo.getClass(), LambdaStructureParser.convertToFieldName(Coupon::getExpansionAmount));
                expansionAmountField.setAccessible(true);
                expansionAmountField.set(couponCoreInfo, null);
            }
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    @Override
    public ApplicableShopCouponCoreInfoVO queryApplicableShopCouponCoreInfo(Long shopId, Boolean isParticipatingInPlatformInflationCouponActivity) {
        // 1. 查询当前用户店铺券（已领取与可领取）
        CompletableFuture<List<CouponCoreInfo>> userShopCouponFuture = CompletableFuture.supplyAsync(() -> this.queryApplicableCouponsForShop(UserProvider.currentUserId(), shopId), contextAwareExecutor);
        // 2. 查询已拥有的平台券 如果当前店铺未参与神券活动，则忽略膨胀信息
        CompletableFuture<List<CouponCoreInfo>> platformCouponFuture = CompletableFuture.supplyAsync(() -> this.queryReceivedOrPurchasablePlatformCoupon(UserProvider.currentUserId(), isParticipatingInPlatformInflationCouponActivity), contextAwareExecutor);
        CompletableFuture.allOf(userShopCouponFuture, platformCouponFuture);
        // 3. result组装
        ApplicableShopCouponCoreInfoVO result = new ApplicableShopCouponCoreInfoVO();
        result.setShopCouponList(userShopCouponFuture.join());
        result.setPlatformCouponList(platformCouponFuture.join());
        return result;
    }

    @Override
    public List<CouponCoreInfo> queryAllAvailableShopCoupons(Long shopId) throws Exception {
        List<CouponCoreInfo> couponCoreInfoList = this.baseMapper.queryIssuedTypeAllEffectiveCoupons(UserProvider.currentUserId(), shopId,IssuedType.SHOP_COUPON.getCode(), CouponState.UNUSED.getCode());
        if (couponCoreInfoList == null || couponCoreInfoList.isEmpty()) {
            return new ArrayList<>();
        }
        // 查询里面优惠券关联的店铺信息
        List<Long> shopIds = couponCoreInfoList.stream().map(CouponCoreInfo::getShopId).distinct().collect(Collectors.toList());
        Map<Long, ShopDisplayInfo> shopDisplayInfoMap = TypeR.extract(shopFeignService.queryShopsDisplayInfo(shopIds)).stream().collect(Collectors.toMap(ShopDisplayInfo::getId, shopDisplayInfo -> shopDisplayInfo));
        return couponCoreInfoList.stream().map(couponCoreInfo -> {
            // 从所有shopDisplayInfoMap中找到ShopCouponVO的店铺id对应的店铺信息
            couponCoreInfo.setShopDisplayInfo(shopDisplayInfoMap.get(couponCoreInfo.getShopId()));
            return couponCoreInfo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<CouponCoreInfo> queryShopsDisplayInfo(Long shopId, List<Long> batchIds) throws Exception {
        CompletableFuture<List<CouponCoreInfo>> couponVOSCompletableFuture = CompletableFuture.supplyAsync(() -> this.baseMapper.receiveSpecificBatchIdCoupon(shopId, batchIds, CouponBatchStatus.CLAIMABLE.getCode()),contextAwareExecutor);
        CompletableFuture<List<ShopDisplayInfo>> shopsDisplayInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return TypeR.extract(shopFeignService.queryShopsDisplayInfo(Arrays.asList(shopId)));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        List<CouponCoreInfo> couponVOS = couponVOSCompletableFuture.get();
        List<ShopDisplayInfo> shopDisplayInfoList = shopsDisplayInfoCompletableFuture.get();
        if (couponVOS == null || couponVOS.isEmpty()) {
            throw new RuntimeException("查询不到指定的店铺券信息！");
        }
        // 查询shopId的店铺基本信息（只会有一个信息）
        if (shopDisplayInfoList == null || shopDisplayInfoList.isEmpty()) {
            throw new RuntimeException("在为店铺优惠查询店铺信息时，查询不到指定的店铺信息！");
        }
        couponVOS.forEach(shopCouponVO -> shopCouponVO.setShopDisplayInfo(shopDisplayInfoList.get(0)));
        return couponVOS;
    }

    @Override
    public void lockCoupons(LockCouponsDTO lockCouponsDTO) {
        List<Long> couponIds = lockCouponsDTO.getCouponIds();
        // 将要进行锁信的优惠券改为已锁定
        LambdaUpdateWrapper<Coupon> lockCouponUpdateWrapper = Wrappers.lambdaUpdate(Coupon.class)
                .set(Coupon::getState, CouponState.LOCKED.getCode())
                .set(Coupon::getOrderId,lockCouponsDTO.getOrderId())
                .in(Coupon::getId, couponIds)
                .eq(Coupon::getState, CouponState.UNUSED.getCode());

        int update = this.baseMapper.update(null, lockCouponUpdateWrapper);
        if (update != couponIds.size()) {
            throw new RuntimeException("优惠券锁定失败！");
        }
    }

    @Override
    public void unLockCoupons(LockCouponsDTO lockCouponsDTO) {
        List<Long> couponIds = lockCouponsDTO.getCouponIds();
        // 将要进行锁信的优惠券改为已锁定
        LambdaUpdateWrapper<Coupon> lockCouponUpdateWrapper = Wrappers.lambdaUpdate(Coupon.class)
                .set(Coupon::getState, CouponState.UNUSED.getCode())
                .set(Coupon::getOrderId,null)
                .in(Coupon::getId, couponIds)
                .eq(Coupon::getState, CouponState.LOCKED.getCode());
        int update = this.baseMapper.update(null, lockCouponUpdateWrapper);
        if (update != couponIds.size()) {
            throw new RuntimeException("优惠券解锁失败！");
        }
    }


    /**
     * 优惠券膨胀
     *
     * @param expansionRule 扩展规则
     * @return {@link BigDecimal}
     */
    private static BigDecimal couponExpansion(String expansionRule) {
        // The original json.
        // String json = "{\"6.00\":100,\"7.00\":5,\"520.00\":1}";

        // Parse the json into a Map.
        Map<String, Number> originalMap = JsonUtils.parse(expansionRule, Map.class);

        // Calculate the total count.
        double total = originalMap.values().stream().mapToDouble(Number::doubleValue).sum();

        // Create a TreeMap which will keep our entries sorted by the cumulative probability.
        TreeMap<Double, String> cumulativeProbabilityMap = new TreeMap<>();

        // Process the original map.
        double cumulativeProbability = 0;
        for (Map.Entry<String, Number> entry : originalMap.entrySet()) {
            String amount = entry.getKey();
            Number count = entry.getValue();
            cumulativeProbability = cumulativeProbability + count.doubleValue() / total * 100;
            cumulativeProbabilityMap.put(cumulativeProbability, amount);
        }

        // Generate a random number between 0~100 (inclusive of fractions)
        double randomNumber = Math.random() * 100;

        // Find the amount corresponding to the randomNumber using stream operations.
        for (Map.Entry<Double, String> entry : cumulativeProbabilityMap.entrySet()) {
            if (entry.getKey() >= randomNumber) {
                return new BigDecimal(entry.getValue());
            }
        }
        throw new RuntimeException("膨胀失败！");
    }


}
